/*-------------------------------------------------------------------------
 *
 * xlogprefetcher.c
 *		恢复的预取支持。
 *
 * Portions Copyright (c) 2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *		src/backend/access/transam/xlogprefetcher.c
 *
 * 此模块提供了一个 XLogReader 的即插即用替代品，旨在通过查看 WAL 中的先前记录
 * 来尽量减少 I/O 等待。如果在不久的将来要访问的块不在缓冲池中，它会启动
 * 可能在调用者最终需要数据之前完成的 I/O。当已引用的块在缓冲池中找到时，
 * 缓冲区会被记录在解码的记录中，以便 XLogReadBufferForRedo() 可以尝试
 * 避免第二次查找缓冲映射表。
 *
 * 当前，仅主分叉被考虑用于预取。目前，预取仅在 BufferPrefetch() 有效的系统（主要是
 * Linux）上有效。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/xlog.h"
#include "access/xlogprefetcher.h"
#include "access/xlogreader.h"
#include "access/xlogutils.h"
#include "catalog/pg_class.h"
#include "catalog/pg_control.h"
#include "catalog/storage_xlog.h"
#include "commands/dbcommands_xlog.h"
#include "utils/fmgrprotos.h"
#include "utils/timestamp.h"
#include "funcapi.h"
#include "pgstat.h"
#include "miscadmin.h"
#include "port/atomics.h"
#include "storage/bufmgr.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/hsearch.h"

/*
 * 每次我们处理这么多WAL时，我们将更新
 * pg_stat_recovery_prefetch中的值。
 */
#define XLOGPREFETCHER_STATS_DISTANCE BLCKSZ

/*
 * 为了检测对同一区块的重复访问并跳过无用的额外系统
 * 调用，我们记住最近预取的区块的一小窗口。
 */
#define XLOGPREFETCHER_SEQ_WINDOW_SIZE 4

/*
 * 当maintenance_io_concurrency未饱和时，我们准备向前查看
 * 多达N倍的区块引用数量。
 */
#define XLOGPREFETCHER_DISTANCE_MULTIPLIER 4

/* 定义以记录内部调试消息。 */
/* #define XLOGPREFETCHER_DEBUG_LEVEL LOG */

/* GUCs */
int			recovery_prefetch = RECOVERY_PREFETCH_TRY;

#ifdef USE_PREFETCH
#define RecoveryPrefetchEnabled() \
		(recovery_prefetch != RECOVERY_PREFETCH_OFF && \
		 maintenance_io_concurrency > 0)
#else
#define RecoveryPrefetchEnabled() false
#endif

static int	XLogPrefetchReconfigureCount = 0;

/*
 * 用于报告是否应开始IO的枚举。
 */
typedef enum
{
	LRQ_NEXT_NO_IO,
	LRQ_NEXT_IO,
	LRQ_NEXT_AGAIN
} LsnReadQueueNextStatus;

/*
 * 可以决定下一个预取哪个区块的回调类型。目前
 * 只有一个。
 */
typedef LsnReadQueueNextStatus (*LsnReadQueueNextFun) (uintptr_t lrq_private,
													   XLogRecPtr *lsn);

/*
 * 一个简单的LSNs循环队列，用于控制
 * (潜在的)进行中的IO数量。这代替了稍后更通用的IO
 * 控制机制，这就是为什么它通过函数指针有看似不必要的
 * 间接。
 */
typedef struct LsnReadQueue
{
	LsnReadQueueNextFun next;
	uintptr_t	lrq_private;
	uint32		max_inflight;
	uint32		inflight;
	uint32		completed;
	uint32		head;
	uint32		tail;
	uint32		size;
	struct
	{
		bool		io;
		XLogRecPtr	lsn;
	}			queue[FLEXIBLE_ARRAY_MEMBER];
} LsnReadQueue;

/*
 * 一个预取器。 这是一个包装XLogReader的机制，预取
 * 将很快被引用的区块，以尽量避免IO阻塞。
 */
struct XLogPrefetcher
{
	/* WAL读取器和当前读取状态。 */
	XLogReaderState *reader;
	DecodedXLogRecord *record;
	int			next_block_id;

	/* 何时发布统计信息。 */
	XLogRecPtr	next_stats_shm_lsn;

	/* 记账以避免访问尚不存在的区块。 */
	HTAB	   *filter_table;
	dlist_head	filter_queue;

	/* 记账以避免重复预取。 */
	RelFileNode recent_rnode[XLOGPREFETCHER_SEQ_WINDOW_SIZE];
	BlockNumber recent_block[XLOGPREFETCHER_SEQ_WINDOW_SIZE];
	int			recent_idx;

	/* 记账以暂时禁用预取。 */
	XLogRecPtr	no_readahead_until;

	/* IO深度管理器。 */
	LsnReadQueue *streaming_read;

	XLogRecPtr	begin_ptr;

	int			reconfigure_count;
};

/*
 * 一个临时过滤器，用于跟踪尚未创建的区块范围、
 * 尚未创建的整个关系，以及（我们假设的）已被删除的整个关系，
 * 或将由批量WAL操作员创建。
 */
typedef struct XLogPrefetcherFilter
{
	RelFileNode rnode;
	XLogRecPtr	filter_until_replayed;
	BlockNumber filter_from_block;
	dlist_node	link;
} XLogPrefetcherFilter;

/*
 * 在共享内存中公开的计数器，用于pg_stat_recovery_prefetch。
 */
typedef struct XLogPrefetchStats
{
	pg_atomic_uint64 reset_time;	/* 上次重置的时间。 */
	pg_atomic_uint64 prefetch;	/* 启动的预取次数。 */
	pg_atomic_uint64 hit;		/* 已在缓存中的区块。 */
	pg_atomic_uint64 skip_init; /* 跳过的零初始化区块。 */
	pg_atomic_uint64 skip_new;	/* 被过滤的新/缺失区块。 */
	pg_atomic_uint64 skip_fpw;	/* 跳过的FPW。 */
	pg_atomic_uint64 skip_rep;	/* 跳过的重复访问。 */

	/* 动态值 */
	int			wal_distance;	/* 超前的WAL字节数。 */
	int			block_distance; /* 超前的区块引用数量。 */
	int			io_depth;		/* 正在进行的I/O数量。 */
} XLogPrefetchStats;

static inline void fc_XLogPrefetcherAddFilter(XLogPrefetcher *fc_prefetcher,
										   RelFileNode fc_rnode,
										   BlockNumber fc_blockno,
										   XLogRecPtr fc_lsn);
static inline bool fc_XLogPrefetcherIsFiltered(XLogPrefetcher *fc_prefetcher,
											RelFileNode fc_rnode,
											BlockNumber fc_blockno);
static inline void fc_XLogPrefetcherCompleteFilters(XLogPrefetcher *fc_prefetcher,
												 XLogRecPtr fc_replaying_lsn);
static LsnReadQueueNextStatus fc_XLogPrefetcherNextBlock(uintptr_t fc_pgsr_private,
													  XLogRecPtr *fc_lsn);

static XLogPrefetchStats *SharedStats;

static inline LsnReadQueue *
fc_lrq_alloc(uint32 fc_max_distance,
		  uint32 fc_max_inflight,
		  uintptr_t fc_lrq_private,
		  LsnReadQueueNextFun fc_next)
{
	LsnReadQueue *fc_lrq;
	uint32		fc_size;

	Assert(fc_max_distance >= fc_max_inflight);

	fc_size = fc_max_distance + 1;	/* 完整的环形缓冲区有间隙 */
	fc_lrq = palloc(offsetof(LsnReadQueue, queue) + sizeof(fc_lrq->queue[0]) * fc_size);
	fc_lrq->lrq_private = fc_lrq_private;
	fc_lrq->max_inflight = fc_max_inflight;
	fc_lrq->size = fc_size;
	fc_lrq->next = fc_next;
	fc_lrq->head = 0;
	fc_lrq->tail = 0;
	fc_lrq->inflight = 0;
	fc_lrq->completed = 0;

	return fc_lrq;
}

static inline void fc_lrq_free(LsnReadQueue *fc_lrq)
{
	pfree(fc_lrq);
}

static inline uint32 fc_lrq_inflight(LsnReadQueue *fc_lrq)
{
	return fc_lrq->inflight;
}

static inline uint32 fc_lrq_completed(LsnReadQueue *fc_lrq)
{
	return fc_lrq->completed;
}

static inline void fc_lrq_prefetch(LsnReadQueue *fc_lrq)
{
	/* 尽量在我们的限制内启动尽可能多的IO。 */
	while (fc_lrq->inflight < fc_lrq->max_inflight &&
		   fc_lrq->inflight + fc_lrq->completed < fc_lrq->size - 1)
	{
		Assert(((fc_lrq->head + 1) % fc_lrq->size) != fc_lrq->tail);
		switch (fc_lrq->next(fc_lrq->lrq_private, &fc_lrq->queue[fc_lrq->head].lsn))
		{
			case LRQ_NEXT_AGAIN:
				return;
			case LRQ_NEXT_IO:
				fc_lrq->queue[fc_lrq->head].io = true;
				fc_lrq->inflight++;
				break;
			case LRQ_NEXT_NO_IO:
				fc_lrq->queue[fc_lrq->head].io = false;
				fc_lrq->completed++;
				break;
		}
		fc_lrq->head++;
		if (fc_lrq->head == fc_lrq->size)
			fc_lrq->head = 0;
	}
}

static inline void fc_lrq_complete_lsn(LsnReadQueue *fc_lrq, XLogRecPtr fc_lsn)
{
	/*
	 * 我们知道'lsn'之前的LSNs已经被重放，因此我们现在可以假设
	 * 之前启动的任何IO都已完成。
	 */
	while (fc_lrq->tail != fc_lrq->head &&
		   fc_lrq->queue[fc_lrq->tail].lsn < fc_lsn)
	{
		if (fc_lrq->queue[fc_lrq->tail].io)
			fc_lrq->inflight--;
		else
			fc_lrq->completed--;
		fc_lrq->tail++;
		if (fc_lrq->tail == fc_lrq->size)
			fc_lrq->tail = 0;
	}
	if (RecoveryPrefetchEnabled())
		fc_lrq_prefetch(fc_lrq);
}

size_t XLogPrefetchShmemSize(void)
{
	return sizeof(XLogPrefetchStats);
}

/*
 * 将所有计数器重置为零。
 */
void XLogPrefetchResetStats(void)
{
	pg_atomic_write_u64(&SharedStats->reset_time, GetCurrentTimestamp());
	pg_atomic_write_u64(&SharedStats->prefetch, 0);
	pg_atomic_write_u64(&SharedStats->hit, 0);
	pg_atomic_write_u64(&SharedStats->skip_init, 0);
	pg_atomic_write_u64(&SharedStats->skip_new, 0);
	pg_atomic_write_u64(&SharedStats->skip_fpw, 0);
	pg_atomic_write_u64(&SharedStats->skip_rep, 0);
}

void XLogPrefetchShmemInit(void)
{
	bool		fc_found;

	SharedStats = (XLogPrefetchStats *)
		ShmemInitStruct("XLogPrefetchStats",
						sizeof(XLogPrefetchStats),
						&fc_found);

	if (!fc_found)
	{
		pg_atomic_init_u64(&SharedStats->reset_time, GetCurrentTimestamp());
		pg_atomic_init_u64(&SharedStats->prefetch, 0);
		pg_atomic_init_u64(&SharedStats->hit, 0);
		pg_atomic_init_u64(&SharedStats->skip_init, 0);
		pg_atomic_init_u64(&SharedStats->skip_new, 0);
		pg_atomic_init_u64(&SharedStats->skip_fpw, 0);
		pg_atomic_init_u64(&SharedStats->skip_rep, 0);
	}
}

/*
 * 当任何影响预取的GUC被更改时调用。
 */
void XLogPrefetchReconfigure(void)
{
	XLogPrefetchReconfigureCount++;
}

/*
 * 在共享内存中递增一个计数器。这相当于在一个
 * 普通的uint64上执行*counter++，没有任何内存屏障或锁定，
 * 除非在那些读取器不可能观察到撕裂值的uint64的平台上。
 */
static inline void fc_XLogPrefetchIncrement(pg_atomic_uint64 *fc_counter)
{
	Assert(AmStartupProcess() || !IsUnderPostmaster);
	pg_atomic_write_u64(fc_counter, pg_atomic_read_u64(fc_counter) + 1);
}

/*
 * 创建一个准备开始预取WAL记录引用的区块的预取器。
 */
XLogPrefetcher *
XLogPrefetcherAllocate(XLogReaderState *fc_reader)
{
	XLogPrefetcher *fc_prefetcher;
	static HASHCTL fc_hash_table_ctl = {
		.keysize = sizeof(RelFileNode),
		.entrysize = sizeof(XLogPrefetcherFilter)
	};

	fc_prefetcher = palloc0(sizeof(XLogPrefetcher));

	fc_prefetcher->reader = fc_reader;
	fc_prefetcher->filter_table = hash_create("XLogPrefetcherFilterTable", 1024,
										   &fc_hash_table_ctl,
										   HASH_ELEM | HASH_BLOBS);
	dlist_init(&fc_prefetcher->filter_queue);

	SharedStats->wal_distance = 0;
	SharedStats->block_distance = 0;
	SharedStats->io_depth = 0;

	/* 首次使用将导致分配streaming_read。 */
	fc_prefetcher->reconfigure_count = XLogPrefetchReconfigureCount - 1;

	return fc_prefetcher;
}

/*
 * 销毁预取器并释放所有资源。
 */
void XLogPrefetcherFree(XLogPrefetcher *fc_prefetcher)
{
	fc_lrq_free(fc_prefetcher->streaming_read);
	hash_destroy(fc_prefetcher->filter_table);
	pfree(fc_prefetcher);
}

/*
 * 提供给读者的访问权限。
 */
XLogReaderState *
XLogPrefetcherGetReader(XLogPrefetcher *fc_prefetcher)
{
	return fc_prefetcher->reader;
}

/*
 * 更新pg_stat_recovery_prefetch视图中可见的统计信息。
 */
void XLogPrefetcherComputeStats(XLogPrefetcher *fc_prefetcher)
{
	uint32		fc_io_depth;
	uint32		fc_completed;
	int64		fc_wal_distance;


	/* 我们现在距离重放还有多远？ */
	if (fc_prefetcher->reader->decode_queue_tail)
	{
		fc_wal_distance =
			fc_prefetcher->reader->decode_queue_tail->lsn -
			fc_prefetcher->reader->decode_queue_head->lsn;
	}
	else
	{
		fc_wal_distance = 0;
	}

	/* 当前有多少IO正在进行和已完成？ */
	fc_io_depth = fc_lrq_inflight(fc_prefetcher->streaming_read);
	fc_completed = fc_lrq_completed(fc_prefetcher->streaming_read);

	/* 更新pg_stat_recovery_prefetch中可见的瞬时统计信息。 */
	SharedStats->io_depth = fc_io_depth;
	SharedStats->block_distance = fc_io_depth + fc_completed;
	SharedStats->wal_distance = fc_wal_distance;

	fc_prefetcher->next_stats_shm_lsn =
		fc_prefetcher->reader->ReadRecPtr + XLOGPREFETCHER_STATS_DISTANCE;
}

/*
 * 一个回调函数，用于检查WAL中的下一个块引用，并可能
 * 启动IO，以便后续读取会更快。
 *
 * 如果没有更多WAL数据可用，则返回LRQ_NEXT_AGAIN。
 *
 * 如果下一个块引用是主分支块，并且它不在缓冲池中，并且
 * 已请求内核开始读取它以使未来的读取系统调用更快，则
 * 返回LRQ_NEXT_IO。一个LSN将被写入*lsn，并且当该LSN被
 * 重放时，将认为该I/O已经完成。
 *
 * 如果检查下一个块引用时发现它已经在缓冲池中，或者出于
 * 各种原因决定不进行预读取，则返回LRQ_NO_IO。
 */
static LsnReadQueueNextStatus
fc_XLogPrefetcherNextBlock(uintptr_t fc_pgsr_private, XLogRecPtr *fc_lsn)
{
	XLogPrefetcher *fc_prefetcher = (XLogPrefetcher *) fc_pgsr_private;
	XLogReaderState *fc_reader = fc_prefetcher->reader;
	XLogRecPtr	fc_replaying_lsn = fc_reader->ReadRecPtr;

	/*
	 * 我们在调用之间跟踪当前的记录和块，
	 * prefetcher->record和prefetcher->next_block_id。
	 */
	for (;;)
	{
		DecodedXLogRecord *fc_record;

		/* 尝试读取新的未来记录，如果我们还没有一个的话。 */
		if (fc_prefetcher->record == NULL)
		{
			bool		fc_nonblocking;

			/*
			 * 如果已经有记录或者有错误排队可以重放，
			 * 我们不想在这里阻塞。否则，可以阻塞等待更多数据：
			 * 假定调用者没有其他事情要做。
			 */
			fc_nonblocking = XLogReaderHasQueuedRecordOrError(fc_reader);

			/* 预读取功能在我们重放到某个点之前被禁用。 */
			if (fc_nonblocking && fc_replaying_lsn <= fc_prefetcher->no_readahead_until)
				return LRQ_NEXT_AGAIN;

			fc_record = XLogReadAhead(fc_prefetcher->reader, fc_nonblocking);
			if (fc_record == NULL)
			{
				/*
				 * 因为错误或在无阻塞模式下缺少数据，我们无法再读取。
				 * 在重放所有已解码的数据之前，不要再尝试提前读取。
				 */
				if (fc_nonblocking && fc_prefetcher->reader->decode_queue_tail)
					fc_prefetcher->no_readahead_until =
						fc_prefetcher->reader->decode_queue_tail->lsn;

				return LRQ_NEXT_AGAIN;
			}

			/*
			 * 如果禁用了预读取，我们不需要分析记录
			 * 或发出任何预读取请求。我们只需要导致一个记录被
			 * 解码。
			 */
			if (!RecoveryPrefetchEnabled())
			{
				*fc_lsn = InvalidXLogRecPtr;
				return LRQ_NEXT_NO_IO;
			}

			/* 我们有一个新的记录需要处理。 */
			fc_prefetcher->record = fc_record;
			fc_prefetcher->next_block_id = 0;
		}
		else
		{
			/* 继续处理上次调用或上次循环中的内容。 */
			fc_record = fc_prefetcher->record;
		}

		/*
		 * 检查那些需要我们过滤块范围的操作，或者
		 * 完全暂停预读取。
		 */
		if (fc_replaying_lsn < fc_record->lsn)
		{
			uint8		fc_rmid = fc_record->header.xl_rmid;
			uint8		fc_record_type = fc_record->header.xl_info & ~XLR_INFO_MASK;

			if (fc_rmid == RM_XLOG_ID)
			{
				if (fc_record_type == XLOG_CHECKPOINT_SHUTDOWN ||
					fc_record_type == XLOG_END_OF_RECOVERY)
				{
					/*
					 * 这些记录可能会改变TLI。如果我们允许“读取TLI”
					 * 和“重放TLI”不同而没有更多分析，避免潜在的
					 * 错误。
					 */
					fc_prefetcher->no_readahead_until = fc_record->lsn;

#ifdef XLOGPREFETCHER_DEBUG_LEVEL
					elog(XLOGPREFETCHER_DEBUG_LEVEL,
						 "suppressing all readahead until %X/%X is replayed due to possible TLI change",
						 LSN_FORMAT_ARGS(fc_record->lsn));
#endif

					/* 继续以便我们可以跳过该记录。 */
				}
			}
			else if (fc_rmid == RM_DBASE_ID)
			{
				/*
				 * 当使用文件复制策略创建数据库时，
				 * 没有WAL记录可以告诉我们关于个别关系的创建。
				 */
				if (fc_record_type == XLOG_DBASE_CREATE_FILE_COPY)
				{
					xl_dbase_create_file_copy_rec *fc_xlrec =
					(xl_dbase_create_file_copy_rec *) fc_record->main_data;
					RelFileNode fc_rnode = {InvalidOid, fc_xlrec->db_id, InvalidOid};

					/*
					 * 在这个数据库被创建之前，不要尝试预读取任何东西，
					 * 否则如果数据库OID或关系文件节点被重用可能会混淆
					 * 不同代的块。与发现关系在磁盘上尚不存在而
					 * 出现ENOENT错误相比，这样做也更加高效。
					 */
					fc_XLogPrefetcherAddFilter(fc_prefetcher, fc_rnode, 0, fc_record->lsn);

#ifdef XLOGPREFETCHER_DEBUG_LEVEL
					elog(XLOGPREFETCHER_DEBUG_LEVEL,
						 "suppressing prefetch in database %u until %X/%X is replayed due to raw file copy",
						 fc_rnode.dbNode,
						 LSN_FORMAT_ARGS(fc_record->lsn));
#endif
				}
			}
			else if (fc_rmid == RM_SMGR_ID)
			{
				if (fc_record_type == XLOG_SMGR_CREATE)
				{
					xl_smgr_create *fc_xlrec = (xl_smgr_create *)
					fc_record->main_data;

					if (fc_xlrec->forkNum == MAIN_FORKNUM)
					{
						/*
						 * 在这个关系被创建之前，不要进行任何预取。
						 * 否则，如果 relfilenode 被重用，我们可能会
						 * 混淆不同代数的块。这也避免了通过额外的系统调用
						 * 来发现问题，这些调用报告 ENOENT。
						 */
						fc_XLogPrefetcherAddFilter(fc_prefetcher, fc_xlrec->rnode, 0,
												fc_record->lsn);

#ifdef XLOGPREFETCHER_DEBUG_LEVEL
						elog(XLOGPREFETCHER_DEBUG_LEVEL,
							 "suppressing prefetch in relation %u/%u/%u until %X/%X is replayed, which creates the relation",
							 fc_xlrec->rnode.spcNode,
							 fc_xlrec->rnode.dbNode,
							 fc_xlrec->rnode.relNode,
							 LSN_FORMAT_ARGS(fc_record->lsn));
#endif
					}
				}
				else if (fc_record_type == XLOG_SMGR_TRUNCATE)
				{
					xl_smgr_truncate *fc_xlrec = (xl_smgr_truncate *)
					fc_record->main_data;

					/*
					 * 在截断范围内，不要考虑进行任何预取
					 * 直到截断被执行。
					 */
					fc_XLogPrefetcherAddFilter(fc_prefetcher, fc_xlrec->rnode,
											fc_xlrec->blkno,
											fc_record->lsn);

#ifdef XLOGPREFETCHER_DEBUG_LEVEL
					elog(XLOGPREFETCHER_DEBUG_LEVEL,
						 "suppressing prefetch in relation %u/%u/%u from block %u until %X/%X is replayed, which truncates the relation",
						 fc_xlrec->rnode.spcNode,
						 fc_xlrec->rnode.dbNode,
						 fc_xlrec->rnode.relNode,
						 fc_xlrec->blkno,
						 LSN_FORMAT_ARGS(fc_record->lsn));
#endif
				}
			}
		}

		/* 从我们上次停下的地方开始扫描块引用。 */
		while (fc_prefetcher->next_block_id <= fc_record->max_block_id)
		{
			int			fc_block_id = fc_prefetcher->next_block_id++;
			DecodedBkpBlock *fc_block = &fc_record->blocks[fc_block_id];
			SMgrRelation fc_reln;
			PrefetchBufferResult fc_result;

			if (!fc_block->in_use)
				continue;

			Assert(!BufferIsValid(fc_block->prefetch_buffer));;

			/*
			 * 记录这个记录的 LSN。当它被重放时，
			 * LsnReadQueue 会将任何提交给早期 LSN 的 IO
			 * 视为完成。
			 */
			*fc_lsn = fc_record->lsn;

			/* 目前我们只尝试为主分支进行预取。 */
			if (fc_block->forknum != MAIN_FORKNUM)
			{
				return LRQ_NEXT_NO_IO;
			}

			/*
			 * 如果有完整的页面图像附加，我们将不会读取这个
			 * 页面，因此不必尝试进行预取。
			 */
			if (fc_block->has_image)
			{
				fc_XLogPrefetchIncrement(&SharedStats->skip_fpw);
				return LRQ_NEXT_NO_IO;
			}

			/* 阅读将被置为零的页面没有意义。 */
			if (fc_block->flags & BKPBLOCK_WILL_INIT)
			{
				fc_XLogPrefetchIncrement(&SharedStats->skip_init);
				return LRQ_NEXT_NO_IO;
			}

			/* 我们应该因为过滤器跳过对这个块的预取吗？ */
			if (fc_XLogPrefetcherIsFiltered(fc_prefetcher, fc_block->rnode, fc_block->blkno))
			{
				fc_XLogPrefetchIncrement(&SharedStats->skip_new);
				return LRQ_NEXT_NO_IO;
			}

			/* 重复预取同一块没有意义。 */
			for (int fc_i = 0; fc_i < XLOGPREFETCHER_SEQ_WINDOW_SIZE; ++fc_i)
			{
				if (fc_block->blkno == fc_prefetcher->recent_block[fc_i] &&
					RelFileNodeEquals(fc_block->rnode, fc_prefetcher->recent_rnode[fc_i]))
				{
					/*
					 * XXX 如果我们还记得它的位置，我们可以设置
					 * recent_buffer，以便恢复可以跳过 smgropen()
					 * 和缓冲区表查找。
					 */
					fc_XLogPrefetchIncrement(&SharedStats->skip_rep);
					return LRQ_NEXT_NO_IO;
				}
			}
			fc_prefetcher->recent_rnode[fc_prefetcher->recent_idx] = fc_block->rnode;
			fc_prefetcher->recent_block[fc_prefetcher->recent_idx] = fc_block->blkno;
			fc_prefetcher->recent_idx =
				(fc_prefetcher->recent_idx + 1) % XLOGPREFETCHER_SEQ_WINDOW_SIZE;

			/*
			 * 我们可以尝试为对同一关系的重复引用提供快速路径
			 * （有一些机制来安全处理失效），但目前我们会
			 * 每次都调用 smgropen()。
			 */
			fc_reln = smgropen(fc_block->rnode, InvalidBackendId);

			/*
			 * 如果关系文件在磁盘上不存在，例如因为我们在崩溃后
			 * 进行重放，而该文件将被创建然后由尚未重放的
			 * WAL 取消链接，在这个记录被重放之前，抑制
			 * 在该关系中的进一步预取。
			 */
			if (!smgrexists(fc_reln, MAIN_FORKNUM))
			{
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
				elog(XLOGPREFETCHER_DEBUG_LEVEL,
					 "suppressing all prefetch in relation %u/%u/%u until %X/%X is replayed, because the relation does not exist on disk",
					 fc_reln->smgr_rnode.node.spcNode,
					 fc_reln->smgr_rnode.node.dbNode,
					 fc_reln->smgr_rnode.node.relNode,
					 LSN_FORMAT_ARGS(fc_record->lsn));
#endif
				fc_XLogPrefetcherAddFilter(fc_prefetcher, fc_block->rnode, 0,
										fc_record->lsn);
				fc_XLogPrefetchIncrement(&SharedStats->skip_new);
				return LRQ_NEXT_NO_IO;
			}

			/*
			 * 如果关系的大小还不够容纳引用的块，在这个记录
			 * 被重放之前，抑制对该块及更高块的预取。
			 */
			if (fc_block->blkno >= smgrnblocks(fc_reln, fc_block->forknum))
			{
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
				elog(XLOGPREFETCHER_DEBUG_LEVEL,
					 "suppressing prefetch in relation %u/%u/%u from block %u until %X/%X is replayed, because the relation is too small",
					 fc_reln->smgr_rnode.node.spcNode,
					 fc_reln->smgr_rnode.node.dbNode,
					 fc_reln->smgr_rnode.node.relNode,
					 fc_block->blkno,
					 LSN_FORMAT_ARGS(fc_record->lsn));
#endif
				fc_XLogPrefetcherAddFilter(fc_prefetcher, fc_block->rnode, fc_block->blkno,
										fc_record->lsn);
				fc_XLogPrefetchIncrement(&SharedStats->skip_new);
				return LRQ_NEXT_NO_IO;
			}

			/* 尝试启动预取。 */
			fc_result = PrefetchSharedBuffer(fc_reln, fc_block->forknum, fc_block->blkno);
			if (BufferIsValid(fc_result.recent_buffer))
			{
				/* 缓存命中，无需操作。 */
				fc_XLogPrefetchIncrement(&SharedStats->hit);
				fc_block->prefetch_buffer = fc_result.recent_buffer;
				return LRQ_NEXT_NO_IO;
			}
			else if (fc_result.initiated_io)
			{
				/* 缓存未命中，I/O（可能）已启动。 */
				fc_XLogPrefetchIncrement(&SharedStats->prefetch);
				fc_block->prefetch_buffer = InvalidBuffer;
				return LRQ_NEXT_IO;
			}
			else
			{
				/*
				 * 这不应该是可能的，因为我们已经确定
				 * 该关系存在于磁盘上并且足够大。
				 * smgrexists()、smgrnblocks() 的缓存失效出现了问题，
				 * 或者文件在我们脚下被取消链接或截断？
				 */
				elog(ERROR,
					 "could not prefetch relation %u/%u/%u block %u",
					 fc_reln->smgr_rnode.node.spcNode,
					 fc_reln->smgr_rnode.node.dbNode,
					 fc_reln->smgr_rnode.node.relNode,
					 fc_block->blkno);
			}
		}

		/*
		 * 一些调用点需要能够读取正好一个记录
		 * 而不进行任何内部预读取。例子：xlog.c 读取
		 * checkpoint 记录，emode 设置为 PANIC，这可能会导致
		 * XLogPageRead() 在某个未来页面上崩溃，以及 xlog.c
		 * 确定下次写入 WAL 的起始位置，这取决于在读取一条记录后
		 * 读取器的内部缓冲区的内容。
		 * 因此，在消耗 XLogPrefetcherBeginRead() 后的第一个记录
		 * 之前，甚至不要考虑预取。
		 */
		if (fc_prefetcher->reader->decode_queue_tail &&
			fc_prefetcher->reader->decode_queue_tail->lsn == fc_prefetcher->begin_ptr)
			return LRQ_NEXT_AGAIN;

		/* 前进到下一条记录。 */
		fc_prefetcher->record = NULL;
	}
	pg_unreachable();
}

/*
 * 暴露有关恢复预取的统计信息。
 */
Datum pg_stat_get_recovery_prefetch(PG_FUNCTION_ARGS)
{
#define PG_STAT_GET_RECOVERY_PREFETCH_COLS 10
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	Datum		fc_values[PG_STAT_GET_RECOVERY_PREFETCH_COLS];
	bool		fc_nulls[PG_STAT_GET_RECOVERY_PREFETCH_COLS];

	InitMaterializedSRF(fcinfo, 0);

	for (int fc_i = 0; fc_i < PG_STAT_GET_RECOVERY_PREFETCH_COLS; ++fc_i)
		fc_nulls[fc_i] = false;

	fc_values[0] = TimestampTzGetDatum(pg_atomic_read_u64(&SharedStats->reset_time));
	fc_values[1] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->prefetch));
	fc_values[2] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->hit));
	fc_values[3] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_init));
	fc_values[4] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_new));
	fc_values[5] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_fpw));
	fc_values[6] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_rep));
	fc_values[7] = Int32GetDatum(SharedStats->wal_distance);
	fc_values[8] = Int32GetDatum(SharedStats->block_distance);
	fc_values[9] = Int32GetDatum(SharedStats->io_depth);
	tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);

	return (Datum) 0;
}

/*
 * 在"lsn"被重放之前，不要从给定的'rnode'预取任何块 >= 'blockno'。
 */
static inline void fc_XLogPrefetcherAddFilter(XLogPrefetcher *fc_prefetcher, RelFileNode fc_rnode,
						BlockNumber fc_blockno, XLogRecPtr fc_lsn)
{
	XLogPrefetcherFilter *fc_filter;
	bool		fc_found;

	fc_filter = hash_search(fc_prefetcher->filter_table, &fc_rnode, HASH_ENTER, &fc_found);
	if (!fc_found)
	{
		/*
		 * 在重放之前，不允许预取此块或更高的块。
		 */
		fc_filter->filter_until_replayed = fc_lsn;
		fc_filter->filter_from_block = fc_blockno;
		dlist_push_head(&fc_prefetcher->filter_queue, &fc_filter->link);
	}
	else
	{
		/*
		 * 我们已经在过滤这个rnode。扩展过滤器的生命周期
		 * 以覆盖这个WAL记录，但保留较低的块编号
		 * 因为我们不想跟踪单个块。
		 */
		fc_filter->filter_until_replayed = fc_lsn;
		dlist_delete(&fc_filter->link);
		dlist_push_head(&fc_prefetcher->filter_queue, &fc_filter->link);
		fc_filter->filter_from_block = Min(fc_filter->filter_from_block, fc_blockno);
	}
}

/*
 * 我们是否重放了导致我们开始过滤块范围的任何记录？
 * 这意味着应该按照需要创建、扩展或删除关系，
 * 所以我们可以停止过滤对给定relfilenode的访问。
 */
static inline void fc_XLogPrefetcherCompleteFilters(XLogPrefetcher *fc_prefetcher, XLogRecPtr fc_replaying_lsn)
{
	while (unlikely(!dlist_is_empty(&fc_prefetcher->filter_queue)))
	{
		XLogPrefetcherFilter *fc_filter = dlist_tail_element(XLogPrefetcherFilter,
														  link,
														  &fc_prefetcher->filter_queue);

		if (fc_filter->filter_until_replayed >= fc_replaying_lsn)
			break;

		dlist_delete(&fc_filter->link);
		hash_search(fc_prefetcher->filter_table, fc_filter, HASH_REMOVE, NULL);
	}
}

/*
 * 检查给定块是否由于过滤器而被跳过。
 */
static inline bool fc_XLogPrefetcherIsFiltered(XLogPrefetcher *fc_prefetcher, RelFileNode fc_rnode,
						 BlockNumber fc_blockno)
{
	/*
	 * 首先测试队列是否为空，因为我们预计它大多数
	 * 时间是空的，这样可以避免哈希表查找。
	 */
	if (unlikely(!dlist_is_empty(&fc_prefetcher->filter_queue)))
	{
		XLogPrefetcherFilter *fc_filter;

		/* 查看块范围是否被过滤。 */
		fc_filter = hash_search(fc_prefetcher->filter_table, &fc_rnode, HASH_FIND, NULL);
		if (fc_filter && fc_filter->filter_from_block <= fc_blockno)
		{
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
			elog(XLOGPREFETCHER_DEBUG_LEVEL,
				 "prefetch of %u/%u/%u block %u suppressed; filtering until LSN %X/%X is replayed (blocks >= %u filtered)",
				 fc_rnode.spcNode, fc_rnode.dbNode, fc_rnode.relNode, fc_blockno,
				 LSN_FORMAT_ARGS(fc_filter->filter_until_replayed),
				 fc_filter->filter_from_block);
#endif
			return true;
		}

		/* 查看整个数据库是否被过滤。 */
		fc_rnode.relNode = InvalidOid;
		fc_rnode.spcNode = InvalidOid;
		fc_filter = hash_search(fc_prefetcher->filter_table, &fc_rnode, HASH_FIND, NULL);
		if (fc_filter)
		{
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
			elog(XLOGPREFETCHER_DEBUG_LEVEL,
				 "prefetch of %u/%u/%u block %u suppressed; filtering until LSN %X/%X is replayed (whole database)",
				 fc_rnode.spcNode, fc_rnode.dbNode, fc_rnode.relNode, fc_blockno,
				 LSN_FORMAT_ARGS(fc_filter->filter_until_replayed));
#endif
			return true;
		}
	}

	return false;
}

/*
 * XLogBeginRead()的一个包装器，同时也重置预取器。
 */
void XLogPrefetcherBeginRead(XLogPrefetcher *fc_prefetcher, XLogRecPtr fc_recPtr)
{
	/* 这将忘记任何正在进行的IO。 */
	fc_prefetcher->reconfigure_count--;

	/* 记账以避免第一次读取时的预读取。 */
	fc_prefetcher->begin_ptr = fc_recPtr;

	fc_prefetcher->no_readahead_until = 0;

	/* 这将忘记解码器中排队的任何记录。 */
	XLogBeginRead(fc_prefetcher->reader, fc_recPtr);
}

/*
 * XLogReadRecord()的一个包装器，它提供相同的接口，
 * 但也会试图为未来WAL记录中引用的块发起I/O。
 */
XLogRecord *
XLogPrefetcherReadRecord(XLogPrefetcher *fc_prefetcher, char **fc_errmsg)
{
	DecodedXLogRecord *fc_record;
	XLogRecPtr	fc_replayed_up_to;

	/*
	 * 查看是否是时候重置预取机制，因为相关的
	 * GUC已被更改。
	 */
	if (unlikely(XLogPrefetchReconfigureCount != fc_prefetcher->reconfigure_count))
	{
		uint32		fc_max_distance;
		uint32		fc_max_inflight;

		if (fc_prefetcher->streaming_read)
			fc_lrq_free(fc_prefetcher->streaming_read);

		if (RecoveryPrefetchEnabled())
		{
			Assert(maintenance_io_concurrency > 0);
			fc_max_inflight = maintenance_io_concurrency;
			fc_max_distance = fc_max_inflight * XLOGPREFETCHER_DISTANCE_MULTIPLIER;
		}
		else
		{
			fc_max_inflight = 1;
			fc_max_distance = 1;
		}

		fc_prefetcher->streaming_read = fc_lrq_alloc(fc_max_distance,
											   fc_max_inflight,
											   (uintptr_t) fc_prefetcher,
											   fc_XLogPrefetcherNextBlock);

		fc_prefetcher->reconfigure_count = XLogPrefetchReconfigureCount;
	}

	/*
	 * 如果有的话，释放最后返回的记录，因为它现在已被
	 * 重放。
	 */
	fc_replayed_up_to = XLogReleasePreviousRecord(fc_prefetcher->reader);

	/*
	 * 我们现在可以取消任何过滤器吗？如果我们在等待创建或
	 * 扩展关系，现在可以访问被覆盖范围内的块。
	 */
	fc_XLogPrefetcherCompleteFilters(fc_prefetcher, fc_replayed_up_to);

	/*
	 * 由早期的WAL发起的所有IO现在都已完成。这可能会触发
	 * 更进一步的预取。
	 */
	fc_lrq_complete_lsn(fc_prefetcher->streaming_read, fc_replayed_up_to);

	/*
	 * 如果还没有任何排队的记录，那么开始预取以使至少
	 * 一条记录被排队。
	 */
	if (!XLogReaderHasQueuedRecordOrError(fc_prefetcher->reader))
	{
		Assert(fc_lrq_inflight(fc_prefetcher->streaming_read) == 0);
		Assert(fc_lrq_completed(fc_prefetcher->streaming_read) == 0);
		fc_lrq_prefetch(fc_prefetcher->streaming_read);
	}

	/* 读取下一条记录。 */
	fc_record = XLogNextRecord(fc_prefetcher->reader, fc_errmsg);
	if (!fc_record)
		return NULL;

	/*
	 * 我们刚刚获得的记录是“当前”记录，方便
	 * XLogRecXXX()宏使用。
	 */
	Assert(fc_record == fc_prefetcher->reader->record);

	/*
	 * 如果maintenance_io_concurrency设置得非常低，
	 * 我们可能在预取一些但不是所有的块，这些块在
	 * 我们即将返回的记录中被引用。通过丢弃预取器
	 * 对它的引用来忘记记录中其余的块。
	 */
	if (fc_record == fc_prefetcher->record)
		fc_prefetcher->record = NULL;

	/*
	 * 查看是否是时候计算一些统计信息，因为已经处理了
	 * 足够的WAL。
	 */
	if (unlikely(fc_record->lsn >= fc_prefetcher->next_stats_shm_lsn))
		XLogPrefetcherComputeStats(fc_prefetcher);

	Assert(fc_record == fc_prefetcher->reader->record);

	return &fc_record->header;
}

bool check_recovery_prefetch(int *fc_new_value, void **fc_extra, GucSource fc_source)
{
#ifndef USE_PREFETCH
	if (*fc_new_value == RECOVERY_PREFETCH_ON)
	{
		GUC_check_errdetail("recovery_prefetch is not supported on platforms that lack posix_fadvise().");
		return false;
	}
#endif

	return true;
}

void assign_recovery_prefetch(int fc_new_value, void *fc_extra)
{
	/* 重新配置预取，因为它依赖的设置已更改。 */
	recovery_prefetch = fc_new_value;
	if (AmStartupProcess())
		XLogPrefetchReconfigure();
}
